package com.logic.config.netty.web.handler;

import java.net.InetSocketAddress;
import java.util.*;

import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.logic.comment.util.JwtUtils;
import com.logic.config.netty.web.constant.WebSocketConstant;
import com.comment.pojo.domain.chat.UserLinkInfo;
import com.logic.config.netty.web.service.WebSocketInfoService;
import com.logic.config.netty.web.model.message.ActionBody;
import com.comment.util.ip.SearcherIp;
import lombok.extern.slf4j.Slf4j;


import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame;
import io.netty.handler.codec.http.websocketx.PingWebSocketFrame;
import io.netty.handler.codec.http.websocketx.PongWebSocketFrame;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketServerHandshaker;
import io.netty.handler.codec.http.websocketx.WebSocketServerHandshakerFactory;
import io.netty.util.CharsetUtil;

import static com.logic.config.netty.web.constant.SessionConstant.CHANEL_INFO_KEY;
import static com.logic.config.netty.web.util.SessionHolder.*;


/**
 * Netty ChannelHandler，用来处理客户端和服务端的会话生命周期事件（握手、建立连接、断开连接、收消息等）
 *
 * @Author
 * @Description 接收请求，接收 WebSocket 信息的控制类
 */
@Slf4j
public class WebSocketSimpleChannelInboundHandler extends SimpleChannelInboundHandler<Object> {
    // WebSocket 握手工厂类
    private final WebSocketServerHandshakerFactory factory = new WebSocketServerHandshakerFactory(
            WebSocketConstant.WEB_SOCKET_URL, null, false
    );
    private WebSocketServerHandshaker handShaker;
    private final WebSocketInfoService websSocketInfoService = new WebSocketInfoService();
    ActionRequestHandleMapping bean = SpringUtil.getBean(ActionRequestHandleMapping.class);

    /**
     * 处理客户端与服务端之间的 websocket 业务
     */
    private void handWebsocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) {
        // 判断是否是关闭 websocket 的指令
        if (frame instanceof CloseWebSocketFrame) {
            // 关闭握手
            handShaker.close(ctx.channel(), (CloseWebSocketFrame) frame.retain());
            websSocketInfoService.clearSession(ctx.channel());
            return;
        }
        // 判断是否是ping消息
        if (frame instanceof PingWebSocketFrame) {
            ctx.channel().write(new PongWebSocketFrame(frame.content().retain()));
            return;
        }
        // 判断是否Pong消息
        if (frame instanceof PongWebSocketFrame) {
            ctx.writeAndFlush(new PongWebSocketFrame(frame.content().retain()));
            return;
        }
        // 判断是否是二进制消息，如果是二进制消息，抛出异常
        if (!(frame instanceof TextWebSocketFrame)) {
            System.out.println("目前我们不支持二进制消息");
            ctx.channel().write(new PongWebSocketFrame(frame.content().retain()));
            throw new RuntimeException("【" + this.getClass().getName() + "】不支持消息");
        }
        // 获取并解析客户端向服务端发送的 json 消息
        String message = ((TextWebSocketFrame) frame).text();

        boolean typeJSON = JSONUtil.isTypeJSON(message);
        if (!typeJSON) {
            // log.error("解析消息异常,原数据:{}", message);
            return;
        }

        ActionBody actionBody = JSONUtil.toBean(message, ActionBody.class);
        if (actionBody.getActionType() == null) {
            log.error("解析消息异常,原数据:{}", message);
        } else {
            try {
                Integer actionType = actionBody.getActionType();
                Object o = "";
                switch (actionType) {
                    case 1:
                        o = bean.obtainActionRes(actionBody);
                        break;
                    case 2:
                        log.info("收到回复消息{}", message);
                        JSONObject data = actionBody.getData();
                        ActionBody.PlayerReplyMessage playerReplyMessage = data.toBean(ActionBody.PlayerReplyMessage.class);
                        replyMessage.put(playerReplyMessage.getMessageCode(), playerReplyMessage);
                        break;
                    default:
                        break;
                }
                ctx.channel().writeAndFlush(new TextWebSocketFrame(o.toString()));
            } catch (Exception e) {
                log.error("转发消息异常:", e);
            }
        }
    }

    /**
     * 客户端与服务端创建连接的时候调用
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) {
    }

    /**
     * 客户端与服务端断开连接的时候调用
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        Channel channel = ctx.channel();
        Object userId;
        try {
            userId = channel.attr(CHANEL_INFO_KEY).get().getUserId();
        } catch (Exception e) {
            userId = "";
        }
        InetSocketAddress inetSocketAddress = (InetSocketAddress) channel.remoteAddress();
        String ip = inetSocketAddress.getHostString();
        String port = String.valueOf(inetSocketAddress.getPort());
        String source = SearcherIp.searcherId(ip);
        websSocketInfoService.clearSession(ctx.channel());
        log.info("————用户离开游戏：{}({}:{}-{}),在线数量:{}————",
                userId, ip, port, source, channelGroup.size()
        );
    }

    /**
     * 服务端接收客户端发送过来的数据结束之后调用
     */
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.flush();
    }

    /**
     * 工程出现异常的时候调用
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        Channel channel = ctx.channel();
        UserLinkInfo userLinkInfo = channel.attr(CHANEL_INFO_KEY).get();
        Long userId =userLinkInfo.getUserId();
        InetSocketAddress inetSocketAddress = (InetSocketAddress) channel.remoteAddress();
        String ip = inetSocketAddress.getHostString();
        String port = String.valueOf(inetSocketAddress.getPort());
        String source = SearcherIp.searcherId(ip);
        log.error("客户端：{}({}:{}-{}),{}",
                userId, ip, port, source, cause.getMessage()
        );
        ctx.close();
    }

    /**
     * 服务端处理客户端websocket请求的核心方法
     */
    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, Object o) throws Exception {
        if (o instanceof FullHttpRequest) {
            // 处理客户端向服务端发起 http 请求的业务
            handHttpRequest(channelHandlerContext, (FullHttpRequest) o);
        } else if (o instanceof WebSocketFrame) {
            // 处理客户端与服务端之间的 websocket 业务
            handWebsocketFrame(channelHandlerContext, (WebSocketFrame) o);
        }
    }

    /**
     * 处理客户端向服务端发起 http 握手请求的业务
     * WebSocket在建立握手时，数据是通过HTTP传输的。但是建立之后，在真正传输时候是不需要HTTP协议的。
     * <p>
     * WebSocket 连接过程：
     * 首先，客户端发起http请求，经过3次握手后，建立起TCP连接；http请求里存放WebSocket支持的版本号等信息，如：Upgrade、Connection、WebSocket-Version等；
     * 然后，服务器收到客户端的握手请求后，同样采用HTTP协议回馈数据；
     * 最后，客户端收到连接成功的消息后，开始借助于TCP传输信道进行全双工通信。
     */
    private void handHttpRequest(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception {
        // 如果请求失败或者该请求不是客户端向服务端发起的 http 请求，则响应错误信息
        if (!request.decoderResult().isSuccess()
                || !("websocket".equals(request.headers().get("Upgrade")))) {
            // code ：400
            sendHttpResponse(ctx, new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST));
            return;
        }
        // 新建一个握手
        handShaker = factory.newHandshaker(request);
        if (handShaker == null) {
            // 如果为空，返回响应：不受支持的 websocket 版本
            WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());
        } else {
            Channel channel = ctx.channel();
            String uri = request.uri();
            Map<String, String> params = HttpUtil.decodeParamMap(uri, CharsetUtil.UTF_8);
            String tokenStr = params.get("token");
            Long playerId = JwtUtils.getPlayerId(tokenStr);
            UserLinkInfo init = UserLinkInfo.init(playerId);
            channel.attr(CHANEL_INFO_KEY).set(init);
            channelGroup.add(channel);
            channelMap.put(playerId, channel);
            handShaker.handshake(channel, request);

            Object userId = channel.attr(CHANEL_INFO_KEY).get().getUserId();
            InetSocketAddress inetSocketAddress = (InetSocketAddress) channel.remoteAddress();
            String ip = inetSocketAddress.getHostString();
            String port = String.valueOf(inetSocketAddress.getPort());
            String source = SearcherIp.searcherId(ip);
            log.info("————新增用户：{}({}:{}-{}),在线数量:{}————",
                    userId, ip, port, source, channelGroup.size()
            );

        }
    }


    /**
     * 服务端向客户端响应消息
     */
    private void sendHttpResponse(ChannelHandlerContext ctx, DefaultFullHttpResponse response) {
        if (response.status().code() != 200) {
            // 创建源缓冲区
            ByteBuf byteBuf = Unpooled.copiedBuffer(response.status().toString(), CharsetUtil.UTF_8);
            // 将源缓冲区的数据传送到此缓冲区
            response.content().writeBytes(byteBuf);
            // 释放源缓冲区
            byteBuf.release();
        }
        // 写入请求，服务端向客户端发送数据
        ChannelFuture channelFuture = ctx.channel().writeAndFlush(response);
        if (response.status().code() != 200) {
            channelFuture.addListener(ChannelFutureListener.CLOSE);
        }
    }
}
